﻿namespace GreenIsomer.Web.Controls.JQuery {
	using System;
	using System.Web.UI;
	using System.Web.UI.WebControls;
	using System.ComponentModel;
	using System.Globalization;
	using System.Diagnostics.CodeAnalysis;

	/// <summary>
	/// Server control used to generate a jQuery UI Dialog widget.
	/// </summary>
	[ParseChildren(false), PersistChildren(true)]
	public class Dialog : JQueryControl {


		#region Constants: Property defaults

		private const bool defaultAutoOpen = true;
		private const bool defaultBgIFrame = false;
		private const bool defaultCloseOnEscape = true;
		private const bool defaultDraggable = true;
		private const int defaultMinHeight = 150;
		private const int defaultMinWidth = 150;
		private const bool defaultModal = false;
		private const bool defaultResizable = true;
		private const bool defaultStack = true;
		private const int defaultWidth = 300;
		private const int defaultZIndex = 1000; //TODO: Missed a property

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new instance of the GreenIsomer.Web.Controls.JQuery.Dialog class.
		/// </summary>
		public Dialog() { }

		#endregion


		#region Properties: Widget initialization options

		/// <summary>
		/// When AutoOpen is true the dialog will open automatically upon initialization. If false it will stay hidden until .dialog("open") is called on it via JavaScript.
		/// This property maps directly to the "autoOpen" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultAutoOpen), Category("Behavior")]
		public virtual bool AutoOpen {
			get { return ViewState.GetValue("AutoOpen", defaultAutoOpen); }
			set { ViewState["AutoOpen"] = value; }
		}

		/// <summary>
		/// When true, the bgiframe plugin will be used to fix the issue in IE6 where select boxes show on top of other elements regardless of zIndex. Requires including the bgiframe plugin. Future versions may not require a separate plugin.
		/// This property maps directly to the "bgiframe" option when the jQuery UI widget is initialized.
		/// </summary>
		[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Bg", Justification = "This property name comes from the client.  Wouldn't otherwise use abbreviation for Background IFrame.")]
		[DefaultValue(defaultBgIFrame), Category("Behavior")]
		public virtual bool BgIFrame {
			get { return ViewState.GetValue("BgIFrame", defaultBgIFrame); }
			set { ViewState["BgIFrame"] = value; }
		}

		/// <summary>
		/// Specifies whether the dialog should close when it has focus and the user presses the escape (ESC) key.
		/// This property maps directly to the "closeOnEscape" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultCloseOnEscape), Category("Behavior")]
		public virtual bool CloseOnEscape {
			get { return ViewState.GetValue("CloseOnEscape", defaultCloseOnEscape); }
			set { ViewState["CloseOnEscape"] = value; }
		}

		/// <summary>
		/// The specified class name(s) will be added to the dialog for additional theming.
		/// This property maps directly to the "dialogClass" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(""), Category("Appearance"), CssClassProperty]
		public virtual string DialogClass {
			get { return ViewState["DialogClass"] as string ?? string.Empty; }
			set { ViewState["DialogClass"] = value; }
		}

		/// <summary>
		/// If set to true the dialog will be draggable by the titlebar.
		/// This property maps directly to the "draggable" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultDraggable), Category("Behavior")]
		public virtual bool Draggable {
			get { return ViewState.GetValue("Draggable", defaultDraggable); }
			set { ViewState["Draggable"] = value; }
		}

		//TODO: Figure out how to register effects with manager.
		/// <summary>
		/// The effect to be used when the dialog is closed.
		/// This property maps directly to the "hide" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(""), Category("Appearance")]
		public virtual string Hide {
			get { return ViewState["Hide"] as string ?? string.Empty; }
			set { ViewState["Hide"] = value; }
		}

		//TODO: Make sure Layout is the right category for these properties
		/// <summary>
		/// The maximum height to which the dialog can be resized, in pixels.
		/// This property maps directly to the "maxHeight" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(null), Category("Layout")]
		public virtual int? MaxHeight {
			get { return ViewState.GetNullableValue<int>("MaxHeight"); }
			set { ViewState.SetNullableValue("MaxHeight", value); }
		}

		/// <summary>
		/// The maximum width to which the dialog can be resized, in pixels.
		/// This property maps directly to the "maxWidth" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(null), Category("Layout")]
		public virtual int? MaxWidth {
			get { return ViewState.GetNullableValue<int>("MaxWidth"); }
			set { ViewState.SetNullableValue("MaxWidth", value); }
		}

		/// <summary>
		/// The minimum height to which the dialog can be resized, in pixels.
		/// This property maps directly to the "minHeight" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultMinHeight), Category("Layout")]
		public virtual int MinHeight {
			get { return ViewState.GetValue("MinHeight", defaultMinHeight); }
			set { ViewState["MinHeight"] = value; }
		}

		/// <summary>
		/// The minimum width to which the dialog can be resized, in pixels.
		/// This property maps directly to the "minWidth" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultMinWidth), Category("Layout")]
		public virtual int MinWidth {
			get { return ViewState.GetValue("MinWidth", defaultMinWidth); }
			set { ViewState["MinWidth"] = value; }
		}

		/// <summary>
		/// If set to true, the dialog will have modal behavior; other items on the page will be disabled (i.e. cannot be interacted with). Modal dialogs create an overlay below the dialog but above other page elements.
		/// This property maps directly to the "modal" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultModal), Category("Behavior")]
		public virtual bool Modal {
			get { return ViewState.GetValue("Modal", defaultModal); }
			set { ViewState["Modal"] = value; }
		}

		/// <summary>
		/// If set to true the dialog will be resizeable.
		/// This property maps directly to the "resizable" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultResizable), Category("Behavior")]
		public virtual bool Resizable {
			get { return ViewState.GetValue("Resizable", defaultResizable); }
			set { ViewState["Resizable"] = value; }
		}

		/// <summary>
		/// The effect to be used when the dialog is opened.
		/// This property maps directly to the "show" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(""), Category("Appearance")]
		public virtual string Show {
			get { return ViewState.GetValue("Show", string.Empty); }
			set { ViewState["Show"] = value; }
		}

		/// <summary>
		/// Specifies whether the dialog will stack on top of other dialogs. This will cause the dialog to move to the front of other dialogs when it gains focus.
		/// This property maps directly to the "stack" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultStack), Category("Behavior")]
		public virtual bool Stack {
			get { return ViewState.GetValue("Stack", defaultStack); }
			set { ViewState["Stack"] = value; }
		}

		/// <summary>
		/// Specifies the title of the dialog.
		/// This property is HTML encoded and mappped to the "title" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(""), Category("Appearance")]
		public virtual string Title {
			get { return ViewState.GetValue("Title", string.Empty); }
			set { ViewState["Title"] = value; }
		}

		//TODO: Consider the fact that WebControl already has a Height/Width.  Should this be hidden with new?  Different field name straying from JQuery?  or since Dialog is a little different from a style="" standpoint, maybe change away from JQueryControl to direct implementation of IJQueryControl (that's what I'm leaning toward).
		/// <summary>
		/// The width of the dialog, in pixels.
		/// This property maps directly to the "width" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultWidth), Category("Layout")]
		public virtual int Width {
			get {
				EnsureChildControls();
				string value = WidthField.Value;
				return string.IsNullOrEmpty(value) ? defaultWidth : (int)decimal.Parse(value, CultureInfo.InvariantCulture);
				//TODO: Haven't seen it happen with Height/Width but sometimes Left/Top come back with a decimal (302.5)...  Check it out, think about it, etc...
			}
			set {
				EnsureChildControls();
				WidthField.Value = value.ToString(CultureInfo.InvariantCulture);
			}
		}

		#endregion


		#region Properties: Subcontrol

		/// <summary>
		/// Gets or sets the hidden field used to round-trip the Left property.
		/// </summary>
		[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "LeftField", Justification = "Left Field not Leftfield")]
		protected virtual PostBackField LeftField { get; set; }

		/// <summary>
		/// Gets the rendered element ID of the hidden field used to round-trip the Left property.
		/// </summary>
		public virtual string LeftClientId {
			get { return LeftField.ClientID; }
		}

		/// <summary>
		/// Gets or sets the hidden field used to round-trip the Top property.
		/// </summary>
		protected virtual PostBackField TopField { get; set; }

		/// <summary>
		/// Gets the rendered element ID of the hidden field used to round-trip the Top property.
		/// </summary>
		public virtual string TopClientId {
			get { return TopField.ClientID; }
		}

		/// <summary>
		/// Gets or sets the hidden field used to round-trip the Width property.
		/// </summary>
		protected virtual PostBackField WidthField { get; set; }

		/// <summary>
		/// Gets the rendered element ID of the hidden field used to round-trip the Width property.
		/// </summary>
		public virtual string WidthClientId {
			get { return WidthField.ClientID; }
		}

		/// <summary>
		/// Gets or sets the hidden field used to round-trip the Height property.
		/// </summary>
		protected virtual PostBackField HeightField { get; set; }

		/// <summary>
		/// Gets the rendered element ID of the hidden field used to round-trip the Height property.
		/// </summary>
		public virtual string HeightClientId {
			get { return HeightField.ClientID; }
		}

		#endregion


		#region Events

		/// <summary>
		/// Occurs on postback when the Left property value has changed since previous render.
		/// </summary>
		[Category("Action")]
		public event EventHandler LeftChanged;  //TODO: Decide if we want Left/Top or just single Position...

		/// <summary>
		/// Occurs on postback when the Top property value has changed since previous render.
		/// </summary>
		[Category("Action")]
		public event EventHandler TopChanged;  //TODO: Decide if we want Left/Top or just single Position...

		/// <summary>
		/// Occurs on postback when the Width property value has changed since previous render.
		/// </summary>
		[Category("Action")]
		public event EventHandler WidthChanged;

		/// <summary>
		/// Occurs on postback when the Height property value has changed since previous render.
		/// </summary>
		[Category("Action")]
		public event EventHandler HeightChanged;

		#endregion


		#region Methods: Event hooks

		/// <summary>
		/// Raises the LeftChanged event.
		/// </summary>
		/// <param name="e">The System.EventArgs object that contains the event data.</param>
		protected virtual void OnLeftChanged(EventArgs e) {
			if (LeftChanged != null) LeftChanged.Invoke(this, e);
		}

		/// <summary>
		/// Raises the TopChanged event.
		/// </summary>
		/// <param name="e">The System.EventArgs object that contains the event data.</param>
		protected virtual void OnTopChanged(EventArgs e) {
			if (TopChanged != null) TopChanged.Invoke(this, e);
		}

		/// <summary>
		/// Raises the WidthChanged event.
		/// </summary>
		/// <param name="e">The System.EventArgs object that contains the event data.</param>
		protected virtual void OnWidthChanged(EventArgs e) {
			if (WidthChanged != null) WidthChanged.Invoke(this, e);
		}

		/// <summary>
		/// Raises the HeightChanged event.
		/// </summary>
		/// <param name="e">The System.EventArgs object that contains the event data.</param>
		protected virtual void OnHeightChanged(EventArgs e) {
			if (HeightChanged != null) HeightChanged.Invoke(this, e);
		}

		#endregion


		#region Methods: Rendering hooks

		/// <summary>
		/// Allows a jQuery server control to reference scripts and stylesheets necessary to render their client markup and widgets.
		/// </summary>
		/// <param name="manager">The GreenIsomer.Web.Controls.JQuery.Manager used to handle style and script referencing in addition to unified generation of load and ready scripts.</param>
		protected override void Register(Manager manager) {

			manager.RegisterScriptFile(Settings.Default.CoreScriptFile);
			manager.RegisterScriptFile(Settings.Default.UiCoreScriptFile);
			manager.RegisterScriptFile(Settings.Default.UiDraggableScriptFile);
			manager.RegisterScriptFile(Settings.Default.UiResizableScriptFile);
			manager.RegisterScriptFile(Settings.Default.UiDialogScriptFile);

			manager.RegisterStyleFile(Settings.Default.UiCoreStyleFile);
			manager.RegisterStyleFile(Settings.Default.UiResizableStyleFile);
			manager.RegisterStyleFile(Settings.Default.UiDialogStyleFile);
			manager.RegisterThemeStyleFile();

			manager.RegisterControl(this);

			EnsureChildControls();
		}

		/// <summary>
		/// Sends server control content to a provided System.Web.UI.HtmlTextWriter object, which writes the content to be rendered to the client.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="writer">A System.Web.UI.HtmlTextWriter that represents the output stream to render HTML content on the client.</param>
		protected override void Render(HtmlTextWriter writer) {

			writer.AddAttribute(HtmlTextWriterAttribute.Id, ClientID);

			//TODO: Let's revisit the whole "degradability versus getting rid of flicker issue"  I don't think this falls under UseCssStyling, perhaps a DegradeForNonJs... Or is that a moot point here?
			if(!MasterUseCssStyling)
				writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "none");

			writer.RenderBeginTag(HtmlTextWriterTag.Div);
			base.Render(writer);
			writer.RenderEndTag();
		}

		/// <summary>
		/// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparatin for posting back or rendering.
		/// This method is used primarily by control developers.
		/// </summary>
		protected override void CreateChildControls() {
			base.CreateChildControls();

			//TODO: The naming has to be resolved (maybe)

			string idBase = ID + ClientIDSeparator;

			LeftField = new PostBackField();
			LeftField.ID = idBase + "Left";
			//TODO: Hook into Position struct
			LeftField.ValueChanged += new EventHandler<PostBackFieldEventArgs>(LeftField_ValueChanged);
			Controls.Add(LeftField);

			TopField = new PostBackField();
			TopField.ID = idBase + "Top";
			//TODO: Hook into Position struct
			TopField.ValueChanged += new EventHandler<PostBackFieldEventArgs>(TopField_ValueChanged);
			Controls.Add(TopField);

			WidthField = new PostBackField();
			WidthField.ID = idBase + "Width";
			WidthField.Value = Width.ToString(CultureInfo.InvariantCulture);
			WidthField.ValueChanged += new EventHandler<PostBackFieldEventArgs>(WidthField_ValueChanged);
			Controls.Add(WidthField);

			HeightField = new PostBackField();
			HeightField.ID = idBase + "Height";
			//TODO: Hook into Height struct
			HeightField.ValueChanged += new EventHandler<PostBackFieldEventArgs>(HeightField_ValueChanged);
			Controls.Add(HeightField);
		}

		/// <summary>
		/// Renders the load script required to generate the client-side jQuery widget.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		protected override void RenderLoadScript(ScriptBuilder script) {
			script.RenderSelector(ClientID);
			script.Append(".dialog(");
			script.RenderProperties(CreateProperties());
			script.Append(");");
		}

		/// <summary>
		/// Creates the properties object that will be rendered as the jQuery UI initialization options.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <returns>A GreenIsomer.Web.Controls.JQuery.Properties object that will become the jQuery UI initialization options.</returns>
		protected override PropertyCollection CreateProperties() {
			PropertyCollection properties = base.CreateProperties();

			bool autoOpen = AutoOpen;
			if(autoOpen != defaultAutoOpen) properties.AddValue("autoOpen", autoOpen);

			bool bgIFrame = BgIFrame;
			if(bgIFrame != defaultBgIFrame) properties.AddValue("bgiframe", bgIFrame);

			bool closeOnEscape = CloseOnEscape;
			if(closeOnEscape != defaultCloseOnEscape) properties.AddValue("closeOnEscape", closeOnEscape);

			string dialogClass = DialogClass;
			if(!string.IsNullOrEmpty(dialogClass)) properties.AddValue("dialogClass", dialogClass);

			bool draggable = Draggable;
			if(draggable != defaultDraggable) properties.AddValue("draggable", draggable);

			if (draggable) { //TODO: Maybe do a True/False/Auto on this, along with hidden fields (MaintainDragOnPostBack)
				ScriptBuilder dragStopScript = new ScriptBuilder();
				RenderDragStopScript(dragStopScript);
				if (dragStopScript.Length != 0) properties.AddScript("dragStop", dragStopScript);
			}

			//TODO: This is a hack for instant gratification
			decimal height;
			if(decimal.TryParse(HeightField.Value, out height)) properties.AddValue("height", (int)height);

			string hide = Hide;
			if(!string.IsNullOrEmpty(hide)) properties.AddValue("hide", hide);

			int? maxHeight = MaxHeight;
			if(maxHeight.HasValue) properties.AddValue("maxHeight", maxHeight.Value);

			int? maxWidth = MaxWidth;
			if(maxWidth.HasValue) properties.AddValue("maxWidth", maxWidth.Value);

			int minHeight = MinHeight;
			if(minHeight != defaultMinHeight) properties.AddValue("minHeight", minHeight);

			int minWidth = MinWidth;
			if(minWidth != defaultMinWidth) properties.AddValue("minWidth", minWidth);

			bool modal = Modal;
			if(modal != defaultModal) properties.AddValue("modal", modal);

			ScriptBuilder openScript = new ScriptBuilder();
			RenderOpenScript(openScript);
			if(openScript.Length != 0) properties.AddScript("open", openScript);

			//TODO: This is a hack for instant gratification
			decimal left; decimal top;
			if(decimal.TryParse(LeftField.Value, out left) && decimal.TryParse(TopField.Value, out top))
				properties.AddValue("position", new int[] { (int)left, (int)top });

			bool resizable = Resizable;
			if(resizable != defaultResizable) properties.AddValue("resizable", resizable);

			if (resizable) { //TODO: Maybe do a True/False/Auto on this, along with hidden fields (MaintainResizeOnPostBack)
				ScriptBuilder resizeStopScript = new ScriptBuilder();
				RenderResizeStopScript(resizeStopScript);
				if (resizeStopScript.Length != 0) properties.AddScript("resizeStop", resizeStopScript);
			}

			string show = Show;
			if(!string.IsNullOrEmpty(show)) properties.AddValue("show", show);

			bool stack = Stack;
			if(stack != defaultStack) properties.AddValue("stack", stack);

			string title = Title;
			if(!string.IsNullOrEmpty(title)) properties.AddValue("title", title, true);

			int width = Width;
			if(width != defaultWidth) properties.AddValue("width", width);

			return properties;
		}

		/// <summary>
		/// Renders the client "open" event script.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		protected virtual void RenderOpenScript(ScriptBuilder script) {
			script.Append("function(event,ui){ $(this).parent().appendTo(");
			script.AppendValue("#" + Page.Form.ClientID);
			script.Append("); }");
		}

		/// <summary>
		/// Renders the client "dragStop" event script.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		protected virtual void RenderDragStopScript(ScriptBuilder script) {

			script.Append("function(event,ui){ ");

			script.RenderSelector(LeftClientId);
			script.Append(".val(ui.position.left); ");

			script.RenderSelector(TopClientId);
			script.Append(".val(ui.position.top); ");

			script.Append("}");
		}

		/// <summary>
		/// Renders the client "resizeStop" event script.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		protected virtual void RenderResizeStopScript(ScriptBuilder script) {

			script.Append("function(event,ui){ ");

			script.RenderSelector(LeftClientId);
			script.Append(".val(ui.position.left); ");

			script.RenderSelector(TopClientId);
			script.Append(".val(ui.position.top); ");

			script.RenderSelector(WidthClientId);
			script.Append(".val(ui.size.width); ");

			script.RenderSelector(HeightClientId);
			script.Append(".val(ui.size.height); ");

			script.Append("}");
		}

		#endregion


		#region Event handlers

		private void LeftField_ValueChanged(object sender, PostBackFieldEventArgs e) {
			OnLeftChanged(EventArgs.Empty);
		}

		private void TopField_ValueChanged(object sender, PostBackFieldEventArgs e) {
			OnTopChanged(EventArgs.Empty);
		}

		private void WidthField_ValueChanged(object sender, PostBackFieldEventArgs e) {
			OnWidthChanged(EventArgs.Empty);
		}

		private void HeightField_ValueChanged(object sender, PostBackFieldEventArgs e) {
			OnHeightChanged(EventArgs.Empty);
		}

		#endregion


	}

}